﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Toolkit.Web.Infastructure;

namespace Toolkit.Web.ViewComponents.DataGrid
{
    public interface IReadOnlyDataGrid<T> where T : class
    {
        IEnumerable<T> DataSource
        {
            get;
        }

        IList<IReadOnlyDataGridColumn<T>> Columns
        {
            get;
        }

        IReadOnlyDataGridSettings Settings
        {
            get;
        }

        DataGridPaginationHelper<T> PaginationHelper
        {
            get;
        }

        DataGridResetHelper<T> ResetHelper
        {
            get;
        }

        DataGridMenuHelper<T> MenuHelper
        {
            get;
        }

        IList<DataGridColumn<T>> GetOrderedColumns();

        bool ContainsColumnsWhichCanBeSorted();

        bool ContainsColumnsWhichCanBeHiddenAndShown();

        bool ContainsColumnsWithDefinedFilters();
    }

    public sealed class DataGrid<T> : ViewComponentBase, IReadOnlyDataGrid<T> where T : class
    {
        public DataGrid(IViewComponentWriter writer, IEnumerable<T> dataSource)
            : this(writer, dataSource, (IPersistentDataGridModel)null)
        {
        }

        public DataGrid(IViewComponentWriter writer, IEnumerable<T> dataSource, IPersistentDataGridModel settings)
            : this(writer, dataSource, (IDataGridInputModel)settings)
        {
        }

        public DataGrid(IViewComponentWriter writer, IEnumerable<T> dataSource, IDataGridInputModel settings)
            : base(writer)
        {
            // Note: dataSource can be null here (for ajax retrieval of data after page load).

            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            DataSource = dataSource;
            Settings = new DataGridSettings();
            Columns = new List<DataGridColumn<T>>();
            AdditionalHiddenTemplates = new List<HtmlTemplate>();
            Menu = new DataGridMenu<T>(this);
            MenuHelper = new DataGridMenuHelper<T>(this);
            PaginationHelper = new DataGridPaginationHelper<T>(this);
            ResetHelper = new DataGridResetHelper<T>(this);
            ClientEventHandlers = new ClientEventHandlers();
            HtmlBuilder = new DataGridHtmlBuilder<T>(this);

            InputSettings = settings;
        }

        public IEnumerable<T> DataSource
        {
            get;
            private set;
        }

        public IDataGridInputModel InputSettings
        {
            get;
            private set;
        }

        public IList<IDataGridColumnInputModel> ColumnInputSettings
        {
            get;
            private set;
        }

        public Action<DataGridColumnFactory<T>> ColumnFactory
        {
            get;
            set;
        }

        public IList<DataGridColumn<T>> Columns
        {
            get;
            set;
        }

        IList<IReadOnlyDataGridColumn<T>> IReadOnlyDataGrid<T>.Columns
        {
            get
            {
                if (Columns != null)
                {
                    return ((IEnumerable<IReadOnlyDataGridColumn<T>>)Columns).ToList();
                }
                return null;
            }
        }

        public Action<DataGridRow<T>> RowAction
        {
            get;
            set;
        }

        public Action<DataGridChildRow<T>> ChildRowAction
        {
            get;
            set;
        }

        public Action<DataGridCell<T>> CellAction
        {
            get;
            set;
        }

        public DataGridSettings Settings
        {
            get;
            set;
        }

        IReadOnlyDataGridSettings IReadOnlyDataGrid<T>.Settings
        {
            get
            {
                if (Settings != null)
                {
                    return (IReadOnlyDataGridSettings)Settings;
                }
                return null;
            }
        }

        public HtmlTemplate LoadingOverlayTemplate
        {
            get;
            set;
        }

        public HtmlTemplate NoResultsTemplate
        {
            get;
            set;
        }

        public HtmlTemplate ErrorLoadingDataTemplate
        {
            get;
            set;
        }

        public HtmlTemplate<IReadOnlyDataGrid<T>> HeaderTemplate
        {
            get;
            set;
        }

        public HtmlTemplate<IReadOnlyDataGrid<T>> FooterTemplate
        {
            get;
            set;
        }

        public DataGridPaginationHelper<T> PaginationHelper
        {
            get;
            private set;
        }

        public DataGridResetHelper<T> ResetHelper
        {
            get;
            private set;
        }

        public IList<HtmlTemplate> AdditionalHiddenTemplates
        {
            get;
            set;
        }

        public DataGridMenu<T> Menu
        {
            get;
            set;
        }

        public DataGridMenuHelper<T> MenuHelper
        {
            get;
            private set;
        }

        public HtmlTemplate DefaultColumnHeaderTemplate
        {
            get;
            set;
        }

        public HtmlTemplate<T> DefaultColumnBodyTemplate
        {
            get;
            set;
        }

        public HtmlTemplate<T> DefaultColumnEditorTemplate
        {
            get;
            set;
        }

        public HtmlTemplate DefaultColumnFooterTemplate
        {
            get;
            set;
        }

        public ClientEventHandlers ClientEventHandlers
        {
            get;
            set;
        }

        public bool HasFilterableColumns()
        {
            if (Columns == null)
            {
                return false;
            }

            return (
                Columns.FirstOrDefault(c => c.IsFilterable && (c.Settings.IsVisible || Settings.RenderHiddenColumns)) != null
            );
        }

        public IList<DataGridColumn<T>> GetOrderedColumns()
        {
            if (Columns == null)
            {
                return null;
            }

            if (Settings.RenderMode == DataGridRenderMode.RowsOnly)
            {
                // Note: When in this render mode, we don't care about any columns that do not have a rendered index.
                return (new List<DataGridColumn<T>>())
                    .Union(Columns.Where(column => column.Settings.RenderedIndex.HasValue).OrderBy(column => column.Settings.RenderedIndex.Value))
                    .ToList();
            }
            else
            {
                return (new List<DataGridColumn<T>>())
                    .Union(Columns.Where(column => column.Settings.LockPositionLeft && column.Settings.Index.HasValue).OrderBy(column => column.Settings.Index.Value))
                    .Union(Columns.Where(column => column.Settings.LockPositionLeft && !column.Settings.Index.HasValue))
                    .Union(Columns.Where(column => !column.Settings.LockPositionLeft && column.Settings.Index.HasValue).OrderBy(column => column.Settings.Index.Value))
                    .Union(Columns.Where(column => !column.Settings.LockPositionLeft && !column.Settings.Index.HasValue))
                    .ToList();
            }
        }

        public bool ContainsColumnsWhichCanBeSorted()
        {
            return (
                Settings != null &&
                Settings.EnableColumnSorting &&
                Columns != null &&
                Columns.FirstOrDefault(column => column.Settings.EnableSorting) != null
            );
        }

        public bool ContainsColumnsWhichCanBeHiddenAndShown()
        {
            return (
                Settings != null &&
                Settings.EnableColumnHidingAndShowing &&
                Columns != null &&
                Columns.FirstOrDefault(column => column.Settings.EnableHidingAndShowing) != null
            );
        }

        public bool ContainsColumnsWithDefinedFilters()
        {
            return (
                Columns != null &&
                Columns.FirstOrDefault(column => column.IsFilterable) != null
            );
        }

        private void ExecuteColumnConfigurations()
        {
            if (ColumnFactory == null)
            {
                throw new Exception("Column configurations not yet set.");
            }

            // Execute column configuration delegate.
            DataGridColumnFactory<T> factory = new DataGridColumnFactory<T>(this);
            ColumnFactory(factory);

            // Execute column filter configuration delegates.
            foreach (DataGridColumn<T> column in Columns)
            {
                DataGridColumnFilterFactory<T> filterFactory = new DataGridColumnFilterFactory<T>(column);
                if (column.FilterFactory != null)
                {
                    column.FilterFactory(filterFactory);
                }
            }
        }

        private void ValidateConfiguration()
        {
            // At least one visible column must be provided.
            if (Columns.Count(c => c.Settings.IsVisible) == 0)
            {
                throw new NotSupportedException("No visible column definitions were provided and no default template was set.");
            }

            // Verify all columns have a key and each key is unique.
            if (Columns.FirstOrDefault(col => string.IsNullOrEmpty(col.Settings.Key)) != null || Columns.GroupBy(col => col.Settings.Key).Count() != Columns.Count)
            {
                throw new NotSupportedException("All columns are required to have been provided a unique column key.");
            }

            // Buffering and ChildRows are not allowed together.
            if (ChildRowAction != null && Settings.ApplyVirtualizedScrolling)
            {
                throw new NotSupportedException("ChildDataRows are not supported when used in conjunction with row buffering.");
            }

            // Virtual paging requires data binding to be in ajax mode.
            if (!Settings.EnableAjaxBinding && Settings.PagingType == DataGridPagingType.VirtualPaging)
            {
                throw new NotSupportedException("Virtual paging requires data to be bound via ajax.");
            }

            // Verify ajax is enabled if no data set was provided.
            if (DataSource == null && !Settings.EnableAjaxBinding)
            {
                throw new NotSupportedException("The delayed loading of data rows until after page load requires data to be bound via ajax.");
            }

            // Require record set size (page size) if paging is enabled.
            if ((Settings.PagingType == DataGridPagingType.BufferedPaging || Settings.PagingType == DataGridPagingType.DefaultPaging || Settings.PagingType == DataGridPagingType.VirtualPaging) && (!Settings.RecordSetSize.HasValue || Settings.RecordSetSize.Value <= 0))
            {
                throw new NotSupportedException("Must provide a record set size (page size) when paging is enabled and size must be greater than 0.");
            }

            // If using server persitence, must have a valid persistence url.
            if (Settings.EnablePersistence && string.IsNullOrEmpty(Settings.PersistenceUrl))
            {
                throw new NotSupportedException("Must provide a valid persistence 'set' url when persistence is enabled.");
            }

            // Verify all column filters have a key and each key is unique.
            foreach (DataGridColumn<T> column in Columns)
            {
                if (column.Filters == null)
                {
                    continue;
                }

                if (column.Filters.FirstOrDefault(f => string.IsNullOrEmpty(f.Key)) != null || column.Filters.GroupBy(f => f.Key).Count() != column.Filters.Count)
                {
                    throw new NotSupportedException("All column filters are required to have been provided a unique column key.");
                }
            }
        }

        private void ApplyInputSettings()
        {
            if (InputSettings != null)
            {
                Settings.RenderMode = InputSettings.RenderMode;

                if (InputSettings.SortByColumnExpression != null && !string.IsNullOrEmpty(InputSettings.SortByColumnExpression.Value))
                {
                    Settings.SortByColumnExpression = new DataExpression(InputSettings.SortByColumnExpression.Value);
                    Settings.IsSortedInDescendingOrder = InputSettings.IsSortedInDescendingOrder;
                }

                Settings.RecordStartIndex = InputSettings.RecordStartIndex;

                if (InputSettings.RecordSetSize.HasValue)
                {
                    Settings.RecordSetSize = InputSettings.RecordSetSize;
                }

                if (InputSettings.MaximumViewPortHeight.HasValue)
                {
                    Settings.MaximumViewPortHeight = InputSettings.MaximumViewPortHeight;
                }

                if (InputSettings.SelectionInput != null)
                {
                    Settings.IncludeInSelectionByDefault = InputSettings.SelectionInput.IncludeInSelectionByDefault;
                    Settings.IncludeInSelection = InputSettings.SelectionInput.IncludeInSelection;
                    Settings.ExcludeFromSelection = InputSettings.SelectionInput.ExcludeFromSelection;
                }

                if (InputSettings.Columns != null)
                {
                    foreach (IDataGridColumnInputModel inputColumn in InputSettings.Columns)
                    {
                        DataGridColumn<T> column = Columns.FirstOrDefault(c => c.Settings.Key == inputColumn.Key);

                        if (column == null)
                        {
                            continue;
                        }

                        if (inputColumn.Width.HasValue)
                        {
                            column.Settings.Width = inputColumn.Width;
                        }

                        if (inputColumn.Index.HasValue)
                        {
                            column.Settings.Index = inputColumn.Index;
                        }

                        column.Settings.IsVisible = inputColumn.IsVisible;

                        if (inputColumn.RenderedIndex.HasValue)
                        {
                            column.Settings.RenderedIndex = inputColumn.RenderedIndex;
                        }

                        if (column.Filters != null && column.Filters.Count > 0 && inputColumn.Filters != null && inputColumn.Filters.Count > 0)
                        {
                            foreach (IDataGridColumnFilterInputModel filterInput in inputColumn.Filters)
                            {
                                DataGridColumnFilter filter = column.Filters.FirstOrDefault(f => 
                                    f.Key == filterInput.Key && 
                                    f.FieldExpression.Value == filterInput.FieldExpression.Value &&
                                    f.FieldValueType.FullName == filterInput.FieldValueType
                                );

                                if (filter == null)
                                {
                                    continue;
                                }

                                filter.AppliedOperator = filterInput.Operator;
                                filter.AppliedValues = filterInput.FieldValues;
                            }
                        }
                    }
                }
            }
        }

        public override void WriteHtml(IViewComponentWriter writer)
        {
            // Note: Must execute column configuration first here so configuration is set before applying persistent settings.
            ExecuteColumnConfigurations();
            ApplyInputSettings();
            ValidateConfiguration();

            base.WriteHtml(writer);
        }
    }
}
